import {
  track,
  trigger,
  ITERATE_KEY,
  TriggerType,
  effect,
} from './reactive-system'

// 解决数组的查找方法，防止在get中重复创建reactive()
const reactiveMap = new Map()

// 数组查找方法的重写
const arrayInstrumentations = {}
;['includes', 'indexOf', 'lastIndexOf'].forEach((methodName) => {
  const originMethod = Array.prototype[methodName]
  arrayInstrumentations[methodName] = function (...args) {
    let res = originMethod.apply(this, args)
    if (res === false || res === -1) {
      res = originMethod.apply(this.raw, args)
    }
    return res
  }
})

// 隐式修改数组length方法的重新
let shouldTrack = true // 是否启用track
;['push', 'pop', 'shift', 'unshift', 'splice'].forEach((methodName) => {
  const originMethod = Array.prototype[methodName]
  arrayInstrumentations[methodName] = function (...args) {
    shouldTrack = false
    const res = originMethod.apply(this, args)
    shouldTrack = true
    return res
  }
})

function createReactive(obj, isShallow = false, isReadonly = false) {
  return new Proxy(obj, {
    has(target, key) {
      track(target, key)
      return Reflect.has(target, key)
    },
    ownKeys(target) {
      // 数组的for...in..., length改变会影响数组的遍历， 要track length
      track(target, Array.isArray(target) ? 'length' : ITERATE_KEY)
      return Reflect.ownKeys(target)
    },
    deleteProperty(target, key) {
      if (isReadonly) {
        console.warn(`属性${key}是只读`)
        return true
      }
      const hasKey = Object.prototype.hasOwnProperty.call(target, key) // 判断是否是自身属性
      const res = Reflect.deleteProperty(target, key)
      if (res && hasKey) {
        trigger(target, key, TriggerType.DELETE)
      }
      return res
    },
    set(target, key, val, receiver) {
      if (isReadonly) {
        console.warn(`属性${key}是只读的`)
        return true
      }
      const type = Array.isArray(target) // 判断数组类型的SET || ADD
        ? +key < target.length
          ? TriggerType.SET
          : TriggerType.ADD
        : Object.prototype.hasOwnProperty.call(target, key)
        ? TriggerType.SET
        : TriggerType.ADD // 要区分是新增属性还是修改属性
      const oldVal = target[key]
      const res = Reflect.set(target, key, val, receiver)
      if (receiver.raw === target) {
        // eslint-disable-next-line no-self-compare
        if (oldVal !== val && (oldVal === oldVal || val === val)) {
          trigger(target, key, type, val) // 针对数组length的修改，要传递val给trigger
        }
      }
      return res
    },
    get(target, key, receiver) {
      if (key === 'raw') {
        return target
      }
      // 重写数组方法
      if (
        Array.isArray(target) &&
        Object.prototype.hasOwnProperty.call(arrayInstrumentations, key)
      ) {
        return Reflect.get(arrayInstrumentations, key, receiver)
      }
      // 因为for..of..遍历的 以及调用数组的values方法，都是调用的[Symbol.iterator], 不应该在effectFn上建立这类symbol值的联系
      if (!isReadonly && typeof key !== 'symbol') {
        track(target, key, shouldTrack) // 在隐式修改数组length的时候，需要禁用track
      }
      const res = Reflect.get(target, key, receiver)
      if (isShallow) return res
      if (typeof res === 'object' && res !== null) {
        return isReadonly ? readonly(res) : reactive(res)
      }
      return res
    },
  })
}

export function reactive(obj) {
  // 防止数组查找的时候，重复的创建reactive
  const existesProxy = reactiveMap.get(obj)
  if (existesProxy) return existesProxy
  else {
    const res = createReactive(obj)
    reactiveMap.set(obj, res)
    return res
  }
}
function readonly(obj) {
  return createReactive(obj, false, true)
}

// ==================1. 数组长度的响应式==================
/* const arr = reactive(['foo'])
effect(() => {
  console.log(arr.length)
})
arr[1] = 'bar' */

// ==================2. 修改数组长度==================
/* const arr = reactive([1])
effect(() => {
  console.log('change arr length:', arr[0])
})
arr.length = 0 */

// ==================3. 遍历数组==================
/* const arr = reactive(['foo'])
// effect(() => {
//   console.count('for..in..遍历数组effect')
//   // for...in...  通过代理的ownKeys方法拦截
//   for (const key in arr) {
//     console.log(arr[key])
//   }
// })

effect(() => {
  console.count('for..of..遍历数组')
  for (const val of arr) {
    console.log(val)
  }
})

// 可能影响数组遍历的操作
arr[0] = 'rnm'
arr[1] = 'bar'
arr.length = 0 */

// ==================4. 数组的查找==================
// const obj = {}
// const arr = reactive([obj])
// console.log(arr.includes(arr[0])) // false有问题
// /*
// 因为在get的时候， return isReadonly ? readonly(res) : reactive(res)
// 访问arr[0]会创建一个obj的代理对象， 而在creative创建arr的时候，调用arr.includes方法内部也会通过arr访问数组元素，从而代用get 获得一个新的代理对象
// 所以返回false
// 解决方式是要存一个原始对象到proxy对象的Map
// */
// console.log(arr.includes(obj)) // false有问题, 还是那个问题， obj是原始对象， includes拿到的是proxy对象

// ==================5.隐式修改数组长度的原型方法==================
/*
push/pop/shift/unshift/splice等方法，会隐式的修改数组长度
以push为例:当调用push的时候，既会读取数组的length， 也会设置数组的length
*/
const arr = reactive([])
effect(() => {
  arr.push(1)
})

effect(() => {
  arr.push(1)
})
